module tyrc_ctrl_path #(
    `include "tyrc_param.v")(
    input 				clk,
    input               rst_n,
    input [WORD_WD -1:0]instruction,     //IR dec inst
    input               zero_flag,       //alu out to zero reg
    
    output              load_r0,         //load bus2 to R0
    output              load_r1,         //load bus2 to R1
    output              load_r2,         //load bus2 to R2
    output              load_r3,         //load bus2 to R3
    output              load_pc,         //load bus2 to PC
    output              load_ir,         //load bus2 to IR
    output              load_reg_y,      //load bus2 to REG_Y
    output              load_reg_z,      //load alu to REG_Z
    output              load_add_r,      //load bus2 to ADDR_R
    output              inc_pc,          //PC incr add
    output [SEL1_WD-1:0]mux_sel_to_bus1, //src = R0/1/2/3/PC, to alu/bus2/mem
    output [SEL2_WD-1:0]mux_sel_to_bus2, //src = alu/bus2/mem, to R0/1/2/3/PC/IR/REG_Y/ADD_R
    output              mem_wr_en        //mem write enable
);
// ==========================================================================
// wire declare
// ==========================================================================
wire inst_need_ex2;
wire [4 -1:0]inst_op;
wire [2 -1:0]inst_src;
wire [2 -1:0]inst_dst;
wire [2 -1:0]inst_addr;

wire long_inst_need_ex = (inst_op == LOAD) || (inst_op == STORE) || (inst_op == BR) || (inst_op == BRZ && zero_flag == 1'b1);

assign {inst_op, inst_src, inst_dst} = instruction;
assign inst_addr                     = instruction;


// ==========================================================================
// inst ctrl status
// ==========================================================================
localparam S_IDLE  = 3'd0;
localparam S_ISF   = 3'd1;
localparam S_ISR   = 3'd2;
localparam S_ISD   = 3'd3;
localparam S_ISE1  = 3'd4;
localparam S_ISE2  = 3'd5;
localparam S_WAIT  = 3'd6;

reg [3 -1:0]ctrl_status;
always @(posedge clk or rst_n)begin
    if(rst_n != 1'b1)begin
        ctrl_status <= S_IDLE;
    end
    else begin
        case(ctrl_status)
            S_IDLE  : ctrl_status <= S_ISF;
            S_ISF   : ctrl_status <= S_ISR;        //PC -> ADD_R
            S_ISR   : ctrl_status <= S_ISD;        //mem -> IR
            S_ISD   : begin                        //get inst
                          if(inst_op == HALT) ctrl_status <= S_WAIT;
                          else                ctrl_status <= S_ISE1;
                      end
            S_ISE1  : begin
                          if(inst_need_ex2) ctrl_status <= S_ISE2;
                          else              ctrl_status <= S_ISF;
                      end
            S_ISE2  : ctrl_status <= S_ISF;
            S_WAIT  : ctrl_status <= S_WAIT;
            default : ctrl_status <= S_IDLE;
        endcase
    end
end

// ==========================================================================
// output logic
// ==========================================================================
//{R3, R2, R1, R0, PC}
assign mux_sel_to_bus1 = (ctrl_status == S_ISF)  ? 5'b00001:
                         (ctrl_status == S_ISD  && long_inst_need_ex)  ? 5'b00001:
                         (ctrl_status == S_ISE1 && inst_dst == CR0_ID) ? 5'b00010:
                         (ctrl_status == S_ISE1 && inst_dst == CR1_ID) ? 5'b00100:
                         (ctrl_status == S_ISE1 && inst_dst == CR2_ID) ? 5'b01000:
                         (ctrl_status == S_ISE1 && inst_dst == CR3_ID) ? 5'b10000:
                         (inst_src    == CR0_ID) ? 5'b00010:
                         (inst_src    == CR1_ID) ? 5'b00100:
                         (inst_src    == CR2_ID) ? 5'b01000:
                         (inst_src    == CR3_ID) ? 5'b10000:
                                                   5'b00000;
//{ALU, BUS1, mem}
assign mux_sel_to_bus2 = (ctrl_status == S_ISE1 && long_inst_need_ex) ? 3'b001 :
                         (ctrl_status == S_ISE1) ? 3'b100:
                         (ctrl_status == S_ISD ) ? 3'b010: //Rx -> REG_Y
                         (ctrl_status == S_ISF ) ? 3'b010: //PC -> ADD_R
                         (ctrl_status == S_ISR ) ? 3'b001:
                         (ctrl_status == S_ISE2) ? 3'b001:
                                                   3'b000;

//common reg
wire inst_ex1_wr_cr_en = (inst_op == ADD) || (inst_op == SUB) || (inst_op == AND) || (inst_op == NOT);
wire inst_ex2_wr_cr_en = (inst_op == LOAD);
wire inst_wr_cr_en     = ((ctrl_status == S_ISE1) && inst_ex1_wr_cr_en) ||
                         ((ctrl_status == S_ISE2) && inst_ex2_wr_cr_en) ;

assign load_r0         = (inst_dst == CR0_ID) && inst_wr_cr_en;
assign load_r1         = (inst_dst == CR1_ID) && inst_wr_cr_en;
assign load_r2         = (inst_dst == CR2_ID) && inst_wr_cr_en;
assign load_r3         = (inst_dst == CR3_ID) && inst_wr_cr_en;

//br brz load_pc
assign load_pc         = (ctrl_status == S_ISE2) && 
                         ((inst_op == BR) || (inst_op == BRZ && zero_flag == 1'b1));

//ir
assign load_ir         = (ctrl_status == S_ISR);

//other
assign load_reg_y      = (ctrl_status == S_ISD)  && inst_ex1_wr_cr_en;
assign load_reg_z      = (ctrl_status == S_ISE1) && inst_ex1_wr_cr_en;
assign load_add_r      = (ctrl_status == S_ISF) || 
                         (ctrl_status == S_ISD  && long_inst_need_ex) || //load pc to add_r
                         (ctrl_status == S_ISE1 && long_inst_need_ex) ;  //load mem_data to add_r
assign inc_pc          = (ctrl_status == S_ISF) || (ctrl_status == S_ISD && long_inst_need_ex);
assign mem_wr_en       = (ctrl_status == S_ISE2) && (inst_op == STORE);

// ==========================================================================
// mid logic
// ==========================================================================
assign inst_need_ex2   = (ctrl_status == S_ISE1) && 
                         ((inst_op == LOAD) || (inst_op == STORE) || (inst_op == BR) || (inst_op == BRZ && zero_flag == 1'b1));

// ==========================================================================
// debug logic
// ==========================================================================
wire inst_id_en = (ctrl_status == S_ISF);
wire [7:0]inst_id_d, inst_id_q;
assign inst_id_d = inst_id_q + 1'b1;

tyrc_dffre #(.WD(8))
u_inst_id(
    .clk(clk),
    .rst_n(rst_n),
    .d(inst_id_d),
    .en(inst_id_en),
    .q(inst_id_q)
);

endmodule
